/*
	Copyright (C) 2013 Make Ramen, LLC
*/

package com.lc.user.express.view;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.util.Log;
import android.widget.ImageView.ScaleType;

public class RoundedDrawable extends Drawable {
	public static final String TAG = "RoundedDrawable";

	private final RectF mBounds = new RectF();
	
	private final RectF mDrawableRect = new RectF();
	private float mCornerRadius;
	
	private final RectF mBitmapRect = new RectF();
	private final BitmapShader mBitmapShader;
	private final Paint mBitmapPaint;
	private final int mBitmapWidth;
	private final int mBitmapHeight;
	
	private final RectF mBorderRect = new RectF();
	private final Paint mBorderPaint;
	private int mBorderWidth;
	private int mBorderColor;
	
	private ScaleType mScaleType = ScaleType.FIT_XY;
	
	private final Matrix mShaderMatrix = new Matrix();
	
	RoundedDrawable(Bitmap bitmap, float cornerRadius, int border, int borderColor) {
		
		mBorderWidth = border;
		mBorderColor = borderColor; 
		
		mBitmapWidth = bitmap.getWidth();
		mBitmapHeight = bitmap.getHeight();
		mBitmapRect.set(0, 0, mBitmapWidth, mBitmapHeight);
		
		mCornerRadius = cornerRadius;
		mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
		mBitmapShader.setLocalMatrix(mShaderMatrix);

		mBitmapPaint = new Paint();
		mBitmapPaint.setAntiAlias(true);
		mBitmapPaint.setShader(mBitmapShader);
		
		mBorderPaint = new Paint();
		mBorderPaint.setAntiAlias(true);
		mBorderPaint.setColor(mBorderColor);
		mBorderPaint.setStrokeWidth(border);
	}
	
	protected void setScaleType(ScaleType scaleType) {
		if (scaleType == null) { scaleType = ScaleType.FIT_XY; }
		if (mScaleType != scaleType) {
			mScaleType = scaleType;
			setMatrix();
		}
	}
	
	protected ScaleType getScaleType() {
		return mScaleType;
	}
	
	private void setMatrix() {
		mBorderRect.set(mBounds);
		mDrawableRect.set(0 + mBorderWidth, 0 + mBorderWidth, mBorderRect.width() - mBorderWidth, mBorderRect.height() - mBorderWidth);
		
		float scale;
        float dx;
        float dy;
        
		switch(mScaleType) {
		case CENTER:
			Log.d(TAG, "CENTER");
			mBorderRect.set(mBounds);
			mDrawableRect.set(0 + mBorderWidth, 0 + mBorderWidth, mBorderRect.width() - mBorderWidth, mBorderRect.height() - mBorderWidth);
			
			mShaderMatrix.set(null);
			mShaderMatrix.setTranslate((int) ((mDrawableRect.width() - mBitmapWidth) * 0.5f + 0.5f), (int) ((mDrawableRect.height() - mBitmapHeight) * 0.5f + 0.5f));
			break;
		case CENTER_CROP:
			Log.d(TAG, "CENTER_CROP");
			mBorderRect.set(mBounds);
			mDrawableRect.set(0 + mBorderWidth, 0 + mBorderWidth, mBorderRect.width() - mBorderWidth, mBorderRect.height() - mBorderWidth);
			
			mShaderMatrix.set(null);

            dx = 0;
            dy = 0;

            if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
                scale = (float) mDrawableRect.height() / (float) mBitmapHeight; 
                dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
            } else {
                scale = (float) mDrawableRect.width() / (float) mBitmapWidth;
                dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
            }

            mShaderMatrix.setScale(scale, scale);
            mShaderMatrix.postTranslate((int) (dx + 0.5f) + mBorderWidth, (int) (dy + 0.5f) + mBorderWidth);
			break;
		case CENTER_INSIDE:
			Log.d(TAG, "CENTER_INSIDE");
			mShaderMatrix.set(null);
            
            if (mBitmapWidth <= mBounds.width() && mBitmapHeight <= mBounds.height()) {
                scale = 1.0f;
            } else {
                scale = Math.min((float) mBounds.width() / (float) mBitmapWidth,
                        (float) mBounds.height() / (float) mBitmapHeight);
            }
            
            dx = (int) ((mBounds.width() - mBitmapWidth * scale) * 0.5f + 0.5f);
            dy = (int) ((mBounds.height() - mBitmapHeight * scale) * 0.5f + 0.5f);

            mShaderMatrix.setScale(scale, scale);
            mShaderMatrix.postTranslate(dx, dy);
            
    		mBorderRect.set(mBitmapRect);
            mShaderMatrix.mapRect(mBorderRect);
            mDrawableRect.set(mBorderRect.left + mBorderWidth, mBorderRect.top + mBorderWidth, mBorderRect.right - mBorderWidth, mBorderRect.bottom - mBorderWidth);
            mShaderMatrix.setRectToRect(mBitmapRect, mDrawableRect, Matrix.ScaleToFit.FILL);
            break;
		case FIT_CENTER:
			mBorderRect.set(mBitmapRect);
			mShaderMatrix.setRectToRect(mBitmapRect, mBounds, Matrix.ScaleToFit.CENTER);
			mShaderMatrix.mapRect(mBorderRect);
			mDrawableRect.set(mBorderRect.left + mBorderWidth, mBorderRect.top + mBorderWidth, mBorderRect.right - mBorderWidth, mBorderRect.bottom - mBorderWidth);
			mShaderMatrix.setRectToRect(mBitmapRect, mDrawableRect, Matrix.ScaleToFit.FILL);
			break;
		case FIT_END:
			mBorderRect.set(mBitmapRect);
			mShaderMatrix.setRectToRect(mBitmapRect, mBounds, Matrix.ScaleToFit.END);
			mShaderMatrix.mapRect(mBorderRect);
			mDrawableRect.set(mBorderRect.left + mBorderWidth, mBorderRect.top + mBorderWidth, mBorderRect.right - mBorderWidth, mBorderRect.bottom - mBorderWidth);
			mShaderMatrix.setRectToRect(mBitmapRect, mDrawableRect, Matrix.ScaleToFit.FILL);
			break;
		case FIT_START:
			mBorderRect.set(mBitmapRect);
			mShaderMatrix.setRectToRect(mBitmapRect, mBounds, Matrix.ScaleToFit.START);
			mShaderMatrix.mapRect(mBorderRect);
			mDrawableRect.set(mBorderRect.left + mBorderWidth, mBorderRect.top + mBorderWidth, mBorderRect.right - mBorderWidth, mBorderRect.bottom - mBorderWidth);
			mShaderMatrix.setRectToRect(mBitmapRect, mDrawableRect, Matrix.ScaleToFit.FILL);
			break;
		case FIT_XY:
		default:
			Log.d(TAG, "DEFAULT TO FILL");
			mBorderRect.set(mBounds);
			mDrawableRect.set(0 + mBorderWidth, 0 + mBorderWidth, mBorderRect.width() - mBorderWidth, mBorderRect.height() - mBorderWidth);
			mShaderMatrix.set(null);
			mShaderMatrix.setRectToRect(mBitmapRect, mDrawableRect, Matrix.ScaleToFit.FILL);
			break;
		}
		mBitmapShader.setLocalMatrix(mShaderMatrix);
	}

	@Override
	protected void onBoundsChange(Rect bounds) {
		Log.i(TAG, "onboundschange: w: " + bounds.width() + "h:" + bounds.height());
		super.onBoundsChange(bounds);
		
		mBounds.set(bounds);

//		if (USE_VIGNETTE) {
//			RadialGradient vignette = new RadialGradient(
//					mDrawableRect.centerX(), mDrawableRect.centerY() * 1.0f / 0.7f, mDrawableRect.centerX() * 1.3f,
//					new int[] { 0, 0, 0x7f000000 }, new float[] { 0.0f, 0.7f, 1.0f },
//					Shader.TileMode.CLAMP);
//
//			Matrix oval = new Matrix();
//			oval.setScale(1.0f, 0.7f);
//			vignette.setLocalMatrix(oval);
//
//			mBitmapPaint.setShader(
//					new ComposeShader(mBitmapShader, vignette, PorterDuff.Mode.SRC_OVER));
//		}
		
		setMatrix();
	}

	@Override
	public void draw(Canvas canvas) {
		Log.w(TAG, "Draw: " + mScaleType.toString());
		if (mBorderWidth > 0) {
			canvas.drawRoundRect(mBorderRect, mCornerRadius, mCornerRadius, mBorderPaint);
			canvas.drawRoundRect(mDrawableRect, Math.max(mCornerRadius - mBorderWidth, 0), Math.max(mCornerRadius - mBorderWidth, 0), mBitmapPaint);
		} else {
			canvas.drawRoundRect(mDrawableRect, mCornerRadius, mCornerRadius, mBitmapPaint);
		}
	}

	@Override
	public int getOpacity() {
		return PixelFormat.TRANSLUCENT;
	}

	@Override
	public void setAlpha(int alpha) {
		mBitmapPaint.setAlpha(alpha);
	}

	@Override
	public void setColorFilter(ColorFilter cf) {
		mBitmapPaint.setColorFilter(cf);
	}

	@Override
	public int getIntrinsicWidth() {
		return mBitmapWidth;
	}
	
	@Override
	public int getIntrinsicHeight() {
		return mBitmapHeight;
	}
	
	public static Bitmap drawableToBitmap(Drawable drawable) {
	    if (drawable instanceof BitmapDrawable) {
	        return ((BitmapDrawable)drawable).getBitmap();
	    }
	    
	    Bitmap bitmap;
	    int width = drawable.getIntrinsicWidth();
	    int height = drawable.getIntrinsicHeight();
	    if (width > 0 && height > 0) {
		    bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
		    Canvas canvas = new Canvas(bitmap); 
		    drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
		    drawable.draw(canvas);
	    } else {
	    	bitmap = null;
	    }

	    return bitmap;
	}
	
	public static Drawable fromDrawable(Drawable drawable, float radius) {
		return fromDrawable(drawable, radius, 0, 0);
	}
	
	public static Drawable fromDrawable(Drawable drawable, float radius, int border, int borderColor) {
		if (drawable != null) {
			if (drawable instanceof TransitionDrawable) {
		    	TransitionDrawable td = (TransitionDrawable) drawable;
		    	int num = td.getNumberOfLayers();
		    	
		    	Drawable[] drawableList = new Drawable[num];
		    	for (int i = 0; i < num; i++) {
		    		Drawable d = td.getDrawable(i);
		    		if (d instanceof ColorDrawable) {
		    			// TODO skip colordrawables for now
		    			drawableList[i] = d;
		    		} else {
			    		drawableList[i] = new RoundedDrawable(drawableToBitmap(td.getDrawable(i)), radius, border, borderColor);
		    		}
		    	}
		    	return new TransitionDrawable(drawableList);
		    } 
			
			Bitmap bm = drawableToBitmap(drawable);
			if (bm != null) {
				return new RoundedDrawable(bm, radius, border, borderColor);
			} else {
				Log.w(TAG, "Failed to create bitmap from drawable!");
			}
		}
		return drawable;
	}

	public float getCornerRadius() {
		return mCornerRadius;
	}

	public int getBorderWidth() {
		return mBorderWidth;
	}

	public int getBorderColor() {
		return mBorderColor;
	}

	public void setCornerRadius(float radius) {
		this.mCornerRadius = radius;
	}

	public void setBorderWidth(int width) {
		this.mBorderWidth = width;
		mBorderPaint.setStrokeWidth(mBorderWidth);
	}

	public void setBorderColor(int color) {
		this.mBorderColor = color;
		mBorderPaint.setColor(color);
	}
}